home *** CD-ROM | disk | FTP | other *** search
/ Aminet 5 / Aminet 5 - March 1995.iso / Aminet / text / misc / pcal_4_5.lha / pcal / pcal.c < prev    next >
Text File  |  1994-10-16  |  49KB  |  1,649 lines

  1. static char  VERSION_STRING[]    = "@(#)pcal v4.5 - generate Postscript calendars";
  2. /*
  3.  * pcal.c - generate PostScript file to print calendar for any month and year
  4.  *
  5.  * The original PostScript code to generate the calendars was written by
  6.  * Patrick Wood (Copyright (c) 1987 by Patrick Wood of Pipeline Associates,
  7.  * Inc.), and authorized for modification and redistribution.  The calendar
  8.  * file inclusion code was originally written in "bs(1)" by Bill Vogel of
  9.  * AT&T.  Patrick's original PostScript was modified and enhanced several
  10.  * times by King Ables, Tim Tessin, and others whose names have regrettably 
  11.  * been lost.  This C version was originally created by Ken Keirnan of Pacific
  12.  * Bell; additional enhancements by Joseph P. Larson, Ed Hand, Andrew Rogers, 
  13.  * Mark Kantrowitz, Joe Brownlee, Andy Fyfe, Steve Grandi, and Geoff Kuenning.
  14.  * The moon routines were originally written by Jef Poskanzer and Craig
  15.  * Leres, and were incorporated into Pcal by Richard Dyson.
  16.  *
  17.  * Contents:
  18.  *
  19.  *        alt_fopen
  20.  *        change_color
  21.  *        check_numargs
  22.  *        color_msg
  23.  *        get_args
  24.  *        get_flag
  25.  *        init_misc
  26.  *        main
  27.  *        set_color
  28.  *        set_debug_flag
  29.  *        usage
  30.  *
  31.  * Revision history:
  32.  *
  33.  *    4.5    AWR    11/16/93    support red:green:blue shading value
  34.  *                    syntax (cf. writefil.c, pcalutil.ps)
  35.  *
  36.  *            11/03/93    widen flag/argument field in usage()
  37.  *
  38.  *            10/01/93    use define_font() and define_shading()
  39.  *                    (cf. pcalutil.c; latter replaces old
  40.  *                    gen_shading()) for font and shading
  41.  *                    redefinition
  42.  *
  43.  *            09/09/93    predefine alternate character set
  44.  *                    mapping name (cf. writefil.c,
  45.  *                    fontmaps.ps, pcaldefs.h)
  46.  *
  47.  *               04/28/93    restructure function definitions so
  48.  *                    function name appears in first column
  49.  *                    (to facilitate searching for definition
  50.  *                    by name)
  51.  *
  52.  *            02/05/93    support -# flag (generate multiple
  53.  *                    copies of each page)
  54.  *
  55.  *            02/11/92    Add support for predefined holidays
  56.  *                    (cf. pcallang.h, readfile.c)
  57.  *
  58.  *    4.4    AWR    02/10/92    Pipe "help" message through filter
  59.  *                    defined by environment variable
  60.  *                    PAGER_ENV (cf. pcaldefs.h)
  61.  *
  62.  *            01/20/92    Add -z flag (extension of change
  63.  *                    suggested by Steve Grandi)
  64.  *
  65.  *            01/15/92    Add "holiday" to -b, -g; expand -b,
  66.  *                    -g functionality to -G, -O; allow
  67.  *                    range of weekday names in all
  68.  *
  69.  *            01/13/92    Support alternate date and title font
  70.  *                    sizes (single-month calendars only)
  71.  *
  72.  *            01/05/92    Support "{<ordinal>} <day_name> <prep>
  73.  *                    <date_spec>" (cf. readfile.c)
  74.  *
  75.  *    4.3    AWR    12/06/91    Attempted to simplify some of the
  76.  *                    mysteries surrounding command-line
  77.  *                    parsing; moved some processing from
  78.  *                    get_args() to new check_numargs()
  79.  *
  80.  *            12/05/91    Search for moon file in directory
  81.  *                    where Pcal lives (cf. moonphas.c)
  82.  *
  83.  *            12/03/91    Add -s flag to override default values
  84.  *                    for date/fill box shading
  85.  *
  86.  *            11/22/91    Use cvt_escape() (new; cf. pcalutil.c)
  87.  *                    to convert escape sequences in command
  88.  *                    line strings
  89.  *
  90.  *            11/18/91    Improve documentation; add init_misc()
  91.  *                    as catch-all for various initializations
  92.  *
  93.  *            10/25/91    Support moon phases as wildcards
  94.  *
  95.  *            10/17/91    Add -Z flag to generate debugging
  96.  *                    information; add a pre-pass through
  97.  *                    command line flags to detect -ZO
  98.  *                    prior to parsing PCAL_OPTS
  99.  *
  100.  *            10/15/91    Revise logic of date file search
  101.  *
  102.  *    4.2    AWR    10/08/91    Add -k and -K flags to control
  103.  *                    positioning of small calendars
  104.  *
  105.  *            10/03/91    Add "note{/<n>}" to select box for
  106.  *                    note text (as per Geoff Kuenning)
  107.  *
  108.  *                    Add -S flag to suppress generation
  109.  *                    of the small calendars
  110.  *
  111.  *            10/02/91    Add -N flag to specify alternate
  112.  *                    heading for notes box
  113.  *
  114.  *                    Allow user to specify alternate notes
  115.  *                    font size (-n <name>/<size>)
  116.  *
  117.  *            10/01/91    Add -u flag to generate version info
  118.  *                    and parameter usage message
  119.  *
  120.  *            09/30/91    Support "if" and "elif" in date file
  121.  *
  122.  *            09/19/91    Add -c flag to generate input file for
  123.  *                    Un*x "calendar" utility
  124.  *
  125.  *    4.11    AWR    08/20/91    Add support for "nearest" keyword
  126.  *                    (as per Andy Fyfe)
  127.  *
  128.  *                    define "whole_year" when -w set
  129.  *
  130.  *            08/21/91    Support %u, %w, %D, %M format specs
  131.  *                    and optional number following %[+-]
  132.  *                    (cf. writefil.c)
  133.  *
  134.  *    4.1    AWR    08/16/91    Add -G flag to print "gray" dates as
  135.  *                    outlined, gray-filled characters
  136.  *
  137.  *                    Fix potential bug in julday() (cf.
  138.  *                    moonphas.c)
  139.  *
  140.  *    4.02    AWR    07/02/91    Add -v flag to print version info only;
  141.  *                    call find_executable() to get true
  142.  *                    program pathname (cf. pcalutil.c);
  143.  *                    add format specifiers to text strings
  144.  *                    (cf. writefil.c)
  145.  *
  146.  *    4.01    AWR    03/19/91    Incorporate revised moonphas.c (q.v.)
  147.  *
  148.  *    4.0    AWR    02/24/91    Add alt_fopen() to search for file
  149.  *                    in alternate path; use to look for
  150.  *                    date file in same directory as
  151.  *                    Pcal executable (as per Floyd Miller)
  152.  *
  153.  *                    Support negative ordinals (cf.
  154.  *                    readfile.c, pcalutil.c)
  155.  *
  156.  *                    Support expressions in preprocessor
  157.  *                    "if{n}def" lines (cf. exprpars.c)
  158.  *
  159.  *                    Support "even", "odd" ordinals (cf.
  160.  *                    readfile.c) and ordinals > 5th
  161.  *
  162.  *                    Support -B (leave unused boxes blank)
  163.  *                    flag
  164.  *
  165.  *                    Separated into moonphas.c, pcal.c,
  166.  *                    pcalutil.c, readfile.c, and writefil.c;
  167.  *                    added support for moon phase file
  168.  *
  169.  *                    Support -w (whole year) flag; fix
  170.  *                    various bugs and nonportable constructs
  171.  *
  172.  *
  173.  *    Parameters:
  174.  *
  175.  *        pcal [opts]        generate calendar for current month/year
  176.  *                    (current year if -w flag specified)
  177.  *
  178.  *        pcal [opts] yy        generate calendar for entire year yy
  179.  *
  180.  *        pcal [opts] mm yy    generate calendar for month mm
  181.  *                    (Jan = 1), year yy (19yy if yy < 100)
  182.  *                    (12 months starting with mm/yy if -w
  183.  *                    specified)
  184.  *
  185.  *        pcal [opts] mm yy n    as above, for n consecutive months (n
  186.  *                    rounded to next multiple of 12 if -w
  187.  *                    specified)
  188.  *
  189.  *    Output:
  190.  *
  191.  *        PostScript file to print calendars for all selected months.
  192.  *
  193.  *    Options:
  194.  *
  195.  *        -I        initialize all parameters to program defaults
  196.  *
  197.  *        -b <DAY>    print specified weekday in black
  198.  *        -g <DAY>    print specified weekday in gray
  199.  *        -O <DAY>    print specified weekday as unfilled outlines
  200.  *        -G <DAY>    print specified weekday as filled outlines
  201.  *                (default: print Saturdays and Sundays in gray)
  202.  *
  203.  *        -s {<DATE>}{/<FILL>}
  204.  *                specify alternate shading values for dates
  205.  *                and fill boxes
  206.  *                (default: dates = 0.8, fill boxes = 0.9)
  207.  *
  208.  *        -d <FONT>{/<SIZE>}
  209.  *                specify alternate font/size for dates
  210.  *                (default: Times-Bold/25)
  211.  *
  212.  *        -n <FONT>{/<SIZE>}
  213.  *                specify alternate font/size for notes in boxes
  214.  *                (default: Helvetica-Narrow/6)
  215.  *
  216.  *        -t <FONT>{/<SIZE>}
  217.  *                specify alternate font for text headings (and
  218.  *                font size for month/year title)
  219.  *                (default: Times-Bold/48)
  220.  *
  221.  *        -D <SYM>    define preprocessor symbol
  222.  *        -U <SYM>    un-define preprocessor symbol
  223.  *
  224.  *        -e        generate empty calendar (ignore date file)
  225.  *
  226.  *        -f <FILE>    specify alternate date file (default:
  227.  *                ~/.calendar on Un*x, SYS$LOGIN:CALENDAR.DAT
  228.  *                on VMS, s:calendar.dat on Amiga; if
  229.  *                environment variable [logical name on VMS]
  230.  *                PCAL_DIR exists, looks there instead; if
  231.  *                not found in either place, looks in same
  232.  *                directory as Pcal executable)
  233.  *
  234.  *        -o <FILE>    specify alternate output file (default:
  235.  *                stdout on Un*x, CALENDAR.PS on VMS, 
  236.  *                RAM:calendar.ps on Amiga)
  237.  *
  238.  *        -L <STRING>    specify left foot string   (default: "")
  239.  *        -C <STRING>    specify center foot string (default: "")
  240.  *        -R <STRING>    specify right foot string  (default: "")
  241.  *
  242.  *        -N <STRING>    specify notes box header (default: "Notes")
  243.  *
  244.  *        -l        generate landscape-mode calendars
  245.  *        -p        generate portrait-mode calendars
  246.  *                (default: landscape-mode)
  247.  *
  248.  *        -h        (command line only) write version information
  249.  *                and full help message to stdout
  250.  *        -u        (command line only) write version information
  251.  *                and parameter usage message to stdout
  252.  *        -v        (command line only) write version information
  253.  *                alone to stdout
  254.  *
  255.  *        -m        draw a small moon icon on the days of the
  256.  *                full, new, and half moons.
  257.  *        -M        draw a small moon icon every day.  
  258.  *                (default: no moons)
  259.  *
  260.  *        -F <DAY>    select alternate day to be displayed as the 
  261.  *                first day of the week (default: Sunday)
  262.  *
  263.  *        -A        dates are in American format (e.g., 10/15/90,
  264.  *                Oct 15) (default)
  265.  *        -E        dates are in European format (e.g., 15.10.90,
  266.  *                15 Oct)
  267.  *
  268.  *        -x <XSCALE>    These two options can be used to change
  269.  *        -y <YSCALE>    the size of the calendar.
  270.  *
  271.  *        -X <XTRANS>    These two options can be used to relocate
  272.  *        -Y <YTRANS>    the position of the calendar on the page.
  273.  *
  274.  *        -j        print Julian dates (day of year)
  275.  *        -J        print Julian dates and days remaining
  276.  *                (default: neither)
  277.  *
  278.  *        -w        print whole year (12 months) per page
  279.  *
  280.  *        -c        generate input for Un*x calendar(1) utility
  281.  *
  282.  *        -z <VALUE>    specify alternate time zone for moon phase
  283.  *                calculation algorithm
  284.  *
  285.  *        -B        leave unused calendar boxes blank
  286.  *
  287.  *
  288.  *    There are many ways to specify these options in addition to using the
  289.  *    command line; this facilitates customization to the user's needs.
  290.  *
  291.  *    If the environment variable (global symbol on VMS) PCAL_OPTS is
  292.  *    present, its value will be parsed as if it were a command line.
  293.  *    Any options specified will override the program defaults.
  294.  *
  295.  *    All options but -[cefhuvDU] may be specified in the date file by
  296.  *    including one or more lines of the form "opt <options>".  Any such
  297.  *    options override any previous values set either as program defaults,
  298.  *    via PCAL_OPTS, or in previous "opt" lines.
  299.  *
  300.  *    Options explicitly specified on the command line in turn override all
  301.  *    of the above.
  302.  *
  303.  *    Any flag which normally takes an argument may also be specified without
  304.  *    an argument; this resets the corresponding option to its default.  -D
  305.  *    alone un-defines all symbols; -U alone has no effect.
  306.  *
  307.  *    Parameters and flags may be mixed on the command line.  In some cases
  308.  *    (e.g., when a parameter follows a flag without its optional argument)
  309.  *    this may lead to ambiguity; the dummy flag '-' (or '--') may be used
  310.  *    to separate them, i.e. "pcal -t - 9 90".
  311.  *
  312.  *
  313.  *    Date file syntax:
  314.  *
  315.  *    1) Basic Syntax:
  316.  *
  317.  *    The following rules describe the syntax of date file entries:
  318.  *
  319.  *      year <year>
  320.  *
  321.  *      opt <options>
  322.  *
  323.  *      note{/<number>} <month_spec> <text>
  324.  *      note{/<number>} <month> <text>
  325.  *
  326.  *      if -A flag (American date formats) specified:
  327.  *        <month_name> <day>{*} {<text>}
  328.  *        <month><sep><day>{<sep><year>}{*} {<text>}
  329.  *
  330.  *      if -E flag (European date formats) specified:
  331.  *        <day>{<sep>} <month_name>{*} {<text>}
  332.  *        <day>{<sep>} <month>{<sep>}{*} {<text>}
  333.  *        <day><sep><month>{<sep><year>}{*} {<text>}
  334.  *
  335.  *      <ordinal> <day_name> in <month_spec>{*} {<text>}
  336.  *      {<ordinal>} <day_name> <prep> <date_spec>
  337.  *
  338.  *      <holiday>
  339.  *
  340.  *    where
  341.  *
  342.  *      {x}          means x is optional
  343.  *
  344.  *      <date_spec> := any of the above date specs (not year, note, or opt)
  345.  *      <month_name> := first 3+ characters of name of month, or "all"
  346.  *      <month_spec> := <month_name>, or "year"
  347.  *      <day_name> := first 3+ characters of name of weekday, "day",
  348.  *            "weekday", "workday", "holiday", "nonweekday",
  349.  *            "nonworkday", "nonholiday", "new_moon",
  350.  *            "first_quarter", "full_moon", or "last_quarter"
  351.  *      <ordinal> := ordinal number ("1st", "2nd", etc.), "first" .. "fifth",
  352.  *            "last", "even", "odd", or "all"
  353.  *      <prep> := "before", "preceding", "after", "following", "nearest",
  354.  *            "on_or_before", or "on_or_after"
  355.  *      <holiday> := "Christmas", "Thanksgiving", etc.
  356.  *
  357.  *      <sep> := one or more non-numeric, non-space, non-'*' characters
  358.  *      <month>, <day>, <year> are the numeric forms
  359.  *
  360.  *      <options> := any command-line option except -[cefhuvDU]
  361.  *
  362.  *    Comments start with '#' (unless escaped by '\') and run through
  363.  *    end-of-line.
  364.  *
  365.  *    Holidays may be flagged by specifying '*' as the last character of the
  366.  *    date field(s), e.g. "10/12* Columbus Day", "July 4* Independence
  367.  *    Day", etc.  Any dates flagged as holidays will be printed in gray, and
  368.  *    any associated text will appear adjacent to the date.
  369.  *
  370.  *    Note that the numeric date formats (mm/dd{/yy}, dd.mm{.yy}) support an
  371.  *    optional year, which will become the subsequent default year.  The
  372.  *    alphabetic date formats (month dd, dd month) do not support a year
  373.  *    field; the "year yy" command is provided to reset the default year.
  374.  *
  375.  *    European dates may also be specified as "dd. mm." and "dd. month" if
  376.  *    desired.
  377.  *
  378.  *    "Floating" days may be specified in the date file as "first Mon in
  379.  *    Sep", "last Mon in May", "4th Thu in Nov", etc.; any word may be used
  380.  *    in place of "in".  "Relative floating" days (e.g. "Fri after 4th Thu
  381.  *    in Nov") are also accepted; they may span month/year bounds.  Pcal
  382.  *    also accepts date specs such as "all Friday{s} in October", "last
  383.  *    Thursday in all", etc., and produces the expected results; "each" and
  384.  *    "every" are accepted as synonyms for "all".  Negative ordinals are
  385.  *    allowed; "-2nd" means "next to last".
  386.  *
  387.  *    "Floating" days may also be specified relative to a fixed date:
  388.  *    "fourth Sunday before 12/25", etc; note, however, that only positive
  389.  *    ordinals are meaningful in this context.
  390.  *
  391.  *    The words "day", "weekday", "workday", and "holiday" may be used as
  392.  *    wildcards: "day" matches any day, "weekday" matches any day normally
  393.  *    printed in black, "workday" matches any day normally printed in black
  394.  *    and not explicitly flagged as a holiday, and "holiday" matches any day
  395.  *    explicitly flagged as a holiday.  "Nonweekday", "nonworkday", and
  396.  *    "nonholiday" are also supported and have the obvious meanings.  Moon
  397.  *    phases may also appear as wildcards; "nm" is accepted as a synonym for
  398.  *    "new_moon", "1q" and "fq" for "first_quarter", "fm" for "full_moon",
  399.  *    and "3q", "lq", and "third_quarter" for "last_quarter".
  400.  *
  401.  *    "Odd" and "even" do not refer to the actual date; instead, "odd" means
  402.  *    "alternate, starting with the first"; "even" means "alternate,
  403.  *    starting with the second".  Thus, "odd Fridays in March" refers to the
  404.  *    first, third, and (if present) fifth Fridays in March - not to those
  405.  *    Fridays falling on odd dates.
  406.  *
  407.  *    "All" refers to each individual month; "year" refers to the year as an
  408.  *    entity.  Thus "odd Fridays in all" refers to the first/third/ fifth
  409.  *    Friday of each month, while "odd Fridays in year" refers to the first
  410.  *    Friday of January and every other Friday thereafter.
  411.  *
  412.  *    Additional notes may be propagated to an empty calendar box by the
  413.  *    inclusion of one or more lines of the form "note{/<number>} <month>
  414.  *    <text>", where <month> may be numeric or alphabetic; "note{/<number>}
  415.  *    all <text>" propagates <text> to each month in the current year.
  416.  *    <number> is an optional positive or negative number specifying the
  417.  *    empty box where the associated text is to be placed: if positive,
  418.  *    Pcal counts forward from the first empty box; if negative, Pcal counts
  419.  *    backward from the last empty box.  Thus, "note/1 ..."  places the
  420.  *    associated text in the first empty box, and "note/-3 ..."  in the
  421.  *    third-to-last; the default is -1 (last empty box).  (Note that if the
  422.  *    -S option is used, it must be specified either on the command line or
  423.  *    prior to any "note" lines in the date file.)
  424.  *
  425.  *
  426.  *    2) Format specifiers:
  427.  *
  428.  *    Pcal also allows format specifiers in the text (and foot strings - cf.
  429.  *    the -L, -C, -R, and -N options); each will be replaced by its
  430.  *    equivalent string as outlined in the table below.  (Most of these are
  431.  *    derived from the strftime() function; %[lnouwMD0+-] are Pcal-specific.)
  432.  *
  433.  *        %a : abbreviated weekday
  434.  *        %A : full weekday
  435.  *        %b : abbreviated month name
  436.  *        %B : full month name
  437.  *        %d : day of month (1-31)
  438.  *        %j : day of year (1-366)
  439.  *        %l : days left in year (0-365)
  440.  *        %m : month (1-12)
  441.  *        %u : week number (1-54)
  442.  *        %U : week number (0-53)
  443.  *        %w : week number (1-54)
  444.  *        %W : week number (0-53)
  445.  *        %y : year w/o century (00-99)
  446.  *        %Y : year w/century
  447.  *        %% : '%' character
  448.  *
  449.  *        %o : print number as ordinal
  450.  *        %0 : print number with leading zeroes
  451.  *        %+ : use following month or year
  452.  *        %- : use previous month or year
  453.  *        %{+N}[DWMY] : adjust date by +N days/weeks/months/years
  454.  *        %{-N}[DWMY] : adjust date by -N days/weeks/months/years
  455.  *
  456.  *    %u considers the week containing 1/1 as week 1 and the following
  457.  *    logical Sunday (the first day of the week as printed; cf. the -F
  458.  *    flag) as the start of week 2; %U considers the first logical Sunday as
  459.  *    the first day of week 1.  %w and %W behave like %u and %U
  460.  *    respectively, but use the first logical Monday instead.  (Note that %w
  461.  *    has a different meaning to strftime().)
  462.  *
  463.  *    %o prints a number as an ordinal, with the appropriate suffix ("st",
  464.  *    "nd", "rd", or "th" in English) appended; for example, "%od" prints
  465.  *    the day of the month as "1st", "2nd", "3rd", etc.
  466.  *
  467.  *    Unlike strftime(), Pcal's default is to print numbers (except %y)
  468.  *    without leading zeroes.  If leading zeroes are desired, the '0' prefix
  469.  *    may be used; for example, "%0j" prints the day of year as 001-365.
  470.  *
  471.  *    %+ and %- direct Pcal to substitute the following/previous month/year
  472.  *    in the following [bBmyY] specifier; for example, "%+B" prints the name
  473.  *    of the next month.
  474.  *
  475.  *    %{[+-]N}[DWMY] do not print anything, but instead adjust the working
  476.  *    date by +-N days (D), weeks (W), months (M), or years (Y); subsequent
  477.  *    format specifiers use the adjusted date instead of the current date.
  478.  *    For example, "%+1M %B %Y" adjusts the date forward by one month and
  479.  *    then prints the resulting month and year ("January 1991" in December,
  480.  *    1990); "%-2W %b %d" adjusts the date backward by two weeks and prints
  481.  *    the resulting month and day ("Jul 26" on August 9).
  482.  *
  483.  *    Such date adjustments are normally cumulative; for example, "%+1Y%-1D"
  484.  *    adjusts the date forward by one year and then backward by one day.  If
  485.  *    %D or %M is specified alone (or if N is zero), Pcal restores the
  486.  *    original date.  (Note that %M has a different meaning to strftime().)
  487.  *
  488.  *    The "Notes" box uses the first of the current month as the default
  489.  *    date.  All foot strings use the first of the current month in single-
  490.  *    month mode and the first of the starting month in whole-year mode.
  491.  *
  492.  *
  493.  *    3) Preprocessing:
  494.  *
  495.  *    Simple cpp-like functionality is provided.  The date file may include
  496.  *    the following commands, which work like their cpp counterparts:
  497.  *
  498.  *        define <sym>
  499.  *        undef <sym>
  500.  *
  501.  *        if{{n}def} <expr>
  502.  *           ...
  503.  *        { elif <expr>
  504.  *           ... }*
  505.  *        { else
  506.  *           ... }
  507.  *        endif
  508.  *
  509.  *        include{?} <file>
  510.  *
  511.  *    Note that these do not start with '#', which is reserved as a comment
  512.  *    character.
  513.  *
  514.  *    <sym> is a symbol name consisting of a letter followed by zero or more
  515.  *    letters, digits, or underscores ('_').  Symbol names are always
  516.  *    treated in a case-insensitive manner.
  517.  *
  518.  *    <expr> is an expression consisting of symbol names joined by the
  519.  *    logical operators (in order of precedence, high to low) '!'  (unary
  520.  *    negate), '&' (and), '^' (exclusive or), and '|' (inclusive or).  '&&'
  521.  *    and '||' are accepted as synonyms for '&' and '|' respectively; the
  522.  *    order of evaluation may be altered by the use of parentheses.  A
  523.  *    symbol whose name is currently defined evaluates to TRUE; one whose
  524.  *    name is not currently defined evaluates to FALSE.  Thus "ifdef A | B |
  525.  *    C" is TRUE if any of the symbols A, B, and C is currently defined, and
  526.  *    "ifdef A & B & C" is TRUE if all of them are.
  527.  *
  528.  *    "ifndef A | B | C" is equivalent to "ifdef !(A | B | C)" (or, using
  529.  *    DeMorgan's Law, "ifdef !A & !B & !C") - in other words, TRUE if none
  530.  *    of the symbols A, B, and C is currently defined.
  531.  *
  532.  *    "if" is accepted as a synonym for "ifdef".
  533.  *
  534.  *    "elif A" is TRUE if A is defined.  Multiple "elif" clauses may appear;
  535.  *    at most one "if{{n}def}", "elif", or "else" clause in a given block
  536.  *    will be processed.
  537.  *
  538.  *    "define" alone deletes all the current definitions; "if{def}" alone is
  539.  *    always false; "ifndef" alone is always true.
  540.  *
  541.  *    The file name in the "include" directive may optionally be surrounded
  542.  *    by "" or <>; in any case, path names are taken to be relative to the
  543.  *    location of the file containing the "include" directive.  If the
  544.  *    string "%y" appears in the file name, it is replaced by the last two
  545.  *    digits of the current year.  The alternate form, "include?", directs
  546.  *    pcal to silently continue if the specified file cannot be opened. 
  547.  *
  548.  *
  549.  *    4) Miscellaneous:
  550.  *
  551.  *    The "-w" flag defines the symbol "whole_year", which may be tested in
  552.  *    the same manner as any user-defined symbol.
  553.  *
  554.  *    Pcal also supports several predefined holidays; type "pcal -h" to
  555.  *    generate a list of these.
  556.  *
  557.  */
  558.  
  559. /*
  560.  * Standard headers:
  561.  */
  562.  
  563. #include <stdio.h>
  564. #include <ctype.h>
  565. #include <time.h>
  566. #include <string.h>
  567.  
  568. /*
  569.  * Pcal-specific definitions:
  570.  */
  571.  
  572. #define MAIN_MODULE    1
  573. #include "pcaldefs.h"
  574. #include "pcalglob.h"
  575. #include "pcallang.h"
  576.  
  577. /*
  578.  * Globals:
  579.  */
  580.  
  581. static int nargs = 0;                /* count of non-flag args  */
  582. static int numargs[MAXARGS];            /* non-flag (numeric) args */
  583. static int init_month, init_year, nmonths;    /* parsed values of above  */
  584.  
  585.  
  586. /*
  587.  * Main program - parse and validate command-line arguments, open files,
  588.  * generate PostScript boilerplate and code to generate calendars.
  589.  *
  590.  * Program structure:
  591.  *
  592.  * For maximum user flexibility, Pcal gives the user several different ways
  593.  * to set program flags and/or override earlier choices.  This necessitates
  594.  * that main() call get_args() (directly or indirectly) several times:
  595.  *
  596.  *    a) to parse the command line, looking only for -Z flags (which turn on
  597.  *       debugging information) and numeric parameters
  598.  *
  599.  *    b) to parse environment variable (global symbol on VMS) PCAL_OPTS, if
  600.  *       defined
  601.  *
  602.  *    c) to parse the command line a second time, looking for options
  603.  *       related to finding/interpreting the date file: -[cefhuvDU]
  604.  *
  605.  *     d) main() calls read_datefile() to read and parse the date file; it
  606.  *       in turn calls get_args() once per "opt" line in the date file
  607.  *
  608.  *    e) to parse the command line one final time, allowing the user to
  609.  *       override any flags other than those listed in c) above
  610.  *
  611.  * The rest of it is straightforward: main() attempts to open the output file
  612.  * (if any), and, if successful, calls write_psfile() to generate the
  613.  * PostScript output (or write_calfile() to generate the "calendar" input).
  614.  * Some minor housekeeping and we're done.
  615.  *
  616.  */
  617. int
  618. #ifdef PROTOS
  619. main(int argc,
  620.      char **argv)
  621. #else
  622. main(argc, argv)
  623.     int argc;
  624.     char **argv;
  625. #endif
  626. {
  627.     FILE *dfp = NULL;        /* date file pointer */
  628.     char *p, *pathlist[10];
  629.     char tmp[STRSIZ];
  630.     int n;
  631.  
  632.     init_misc();            /* handle initialization warts */
  633.  
  634.     /* extract root program name and program path - note that some
  635.      * systems supply the full pathname and others just the root
  636.      */
  637.  
  638.     strcpy(progname, **argv ? *argv : "pcal");
  639.  
  640.     if ((p = strrchr(progname, END_PATH)) != NULL)
  641.         strcpy(progname, ++p);
  642. #ifndef UN_X
  643.     if ((p = strchr(progname, '.')) != NULL)
  644.         *p = '\0';        /* strip suffix if non-Un*x */
  645. #endif
  646.  
  647.     mk_path(progpath, find_executable(*argv));
  648.  
  649.     /* get version from VERSION_STRING (for use in PostScript comment) */
  650.     strcpy(tmp, VERSION_STRING + 4);
  651.     p = strchr(tmp, ' ') + 1;    /* skip program name */
  652.     *strchr(p, ' ') = '\0';        /* terminate after version */
  653.     strcpy(version, p);
  654.  
  655.     /*
  656.      * Get the arguments from a) the command line (pre-pass to pick up
  657.      * debug flags only), b) the environment variable PCAL_OPTS, c) the
  658.      * first command line pass, d) "opt" lines in the date file, and e)
  659.      * a final command line pass, in that order
  660.      */
  661.  
  662.     /* make an preliminary pass to look for the debug flags (to ensure
  663.      * ensure that -ZO will print any flags set in PCAL_OPTS); also
  664.      * get and validate the numeric command-line arguments
  665.      */
  666.     if (!get_args(argv, P_CMD0, NULL, TRUE)) {
  667.         usage(stderr, FALSE);        /* invalid flag or param */
  668.         exit(EXIT_FAILURE);
  669.     }
  670.  
  671.     /* parse environment variable PCAL_OPTS as a command line */
  672.  
  673.     if ((p = getenv(PCAL_OPTS)) != NULL) {
  674.         strcpy(lbuf, "pcal ");        /* dummy program name */
  675.         strcat(lbuf, p);
  676.         (void) loadwords(words, lbuf);    /* split string into words */
  677.         if (! get_args(words, P_ENV, PCAL_OPTS, FALSE)) {
  678.             usage(stderr, FALSE);
  679.             exit(EXIT_FAILURE);
  680.         }
  681.     }
  682.  
  683.     /* parse command-line arguments once to find name of date file, etc. */
  684.  
  685.     (void) get_args(argv, P_CMD1, NULL, FALSE);
  686.  
  687.     /* Attempt to open the date file as specified by the [-e | -f] flags */
  688.  
  689.     switch (datefile_type) {
  690.     case NO_DATEFILE:
  691.         dfp = NULL;
  692.         break;
  693.  
  694.     case USER_DATEFILE:    
  695.         /* Attempt to open user-specified calendar file: search
  696.          * first in the current directory, then in PCAL_DIR (if
  697.          * defined), and finally in the directory where the Pcal
  698.          * executable lives.  It is a fatal error if the
  699.          * user-specified date file cannot be found.
  700.          */
  701.         n = 0;
  702.         pathlist[n++] = "";
  703.         if ((p = trnlog(PCAL_DIR)) != NULL)
  704.             pathlist[n++] = p;
  705.         pathlist[n++] = progpath;
  706.         pathlist[n] = NULL;
  707.         
  708.         strcpy(tmp, datefile);    /* save original name for error msg */
  709.  
  710.         if ((dfp = alt_fopen(datefile, tmp, pathlist, "r")) == NULL) {
  711.             FPR(stderr, E_FOPEN_ERR, progname, tmp);
  712.             exit(EXIT_FAILURE);
  713.         }
  714.         break;
  715.  
  716.     case SYS_DATEFILE:
  717.         /* Attempt to open system-specified calendar file: search
  718.          * first in PCAL_DIR, then in HOME_DIR (current directory
  719.          * if neither is defined) and finally in the directory where
  720.          * the Pcal executable lives.  It is not an error if the
  721.          * system-specified date file cannot be found; Pcal will
  722.          * simply generate an empty calendar.
  723.          */
  724.         n = 0;
  725.         if ((p = trnlog(PCAL_DIR)) != NULL)
  726.             pathlist[n++] = p;
  727.         if ((p = trnlog(HOME_DIR)) != NULL)
  728.             pathlist[n++] = p;
  729.         if (n == 0)
  730.             pathlist[n++] = "";
  731.         pathlist[n++] = progpath;
  732.         pathlist[n] = NULL;
  733.         
  734.         dfp = alt_fopen(datefile, DATEFILE, pathlist, "r");
  735.  
  736.         /* if the date file has not been found and ALT_DATEFILE is
  737.          * defined, search same paths for ALT_DATEFILE before
  738.          * giving up
  739.          */
  740. #ifdef ALT_DATEFILE
  741.         if (!dfp)
  742.             dfp = alt_fopen(datefile, ALT_DATEFILE, pathlist, "r");
  743. #endif
  744.         break;
  745.     }
  746.  
  747.     /* read the date file (if any) and build internal data structure */
  748.  
  749.     if (dfp) {
  750.         curr_year = init_year;
  751.         read_datefile(dfp, datefile);
  752.         fclose(dfp);
  753.     } else
  754.         datefile[0] = '\0';        /* for PostScript comment */
  755.  
  756.     /* reparse command line - flags there supersede those in date file */
  757.  
  758.     (void) get_args(argv, P_CMD2, NULL, FALSE);
  759.  
  760.     /* if in whole-year mode, round number of months up to full year and
  761.      * set default starting month to January of current year
  762.      */
  763.     if (do_whole_year) {
  764.         nmonths = ((nmonths + 11) / 12) * 12;
  765.         if (nargs == 0)
  766.             init_month = JAN;
  767.  
  768.         /* also disable some meaningless flags */
  769.         draw_moons = NO_MOONS;
  770.         julian_dates = NO_JULIANS;
  771.     }
  772.  
  773.     /* select an appropriate color for holidays if not set explicitly
  774.      */
  775.     if (holiday_color == HOLIDAY_DEFAULT)
  776.         holiday_color = select_color();
  777.  
  778.     /* done with the arguments and flags - try to open the output file */
  779.  
  780.     if (*outfile && freopen(outfile, "w", stdout) == (FILE *) NULL) {
  781.         FPR(stderr, E_FOPEN_ERR, progname, outfile);
  782.         exit(EXIT_FAILURE);
  783.     }
  784.  
  785.     /* generate the "calendar" or PostScript code (cf. writefil.c) */
  786.  
  787.     if (calendar_out)
  788.         write_calfile(init_month, init_year, nmonths);
  789.     else
  790.         write_psfile(init_month, init_year, nmonths);
  791.     
  792.     cleanup();        /* free allocated data */
  793.  
  794.     /* if output was written to a non-obvious location, tell user where */
  795.  
  796. #ifdef DEFAULT_OUTFILE
  797.     FPR(stderr, I_OUT_NAME, progname, outfile);
  798. #endif
  799.  
  800.     exit(EXIT_SUCCESS);
  801. }
  802.  
  803.  
  804. /*
  805.  * init_misc - various initializations not easily handled in .h files
  806.  */
  807. void
  808. #ifdef PROTOS
  809. init_misc(void)
  810. #else
  811. init_misc()
  812. #endif
  813. {
  814.     INIT_COLORS;        /* copy default_color to day_color */
  815.     strcpy(notes_hdr, default_notes_hdr);    /* initialize notes_hdr */
  816. }
  817.  
  818.  
  819. /*
  820.  * set_color - set one or all weekdays (or all holidays) to print in selected
  821.  * color; also accept range such as mon-fri or sat-sun
  822.  */
  823. void
  824. #ifdef PROTOS
  825. set_color(char *day,        /* weekday name (or "all" or "holiday") */
  826.       int col)        /* select black/gray/outline/outline-gray */
  827. #else
  828. set_color(day, col)
  829.     char *day;        /* weekday name (or "all" or "holiday") */
  830.     int  col;        /* select black/gray/outline/outline-gray */
  831. #endif
  832. {
  833.     int i, j, max, count[NUM_COLORS];
  834.     char tmp[STRSIZ], *p;
  835.  
  836.     if (ci_strncmp(day, ALL, strlen(ALL)) == 0) {    /* set all days */
  837.         for (i = 0; i < 7; i++)
  838.             day_color[i] = col;
  839.     }
  840.  
  841.     else if (ci_strncmp(day, HOLIDAY, MIN_DAY_LEN) == 0) /* set holidays */
  842.         holiday_color = col;
  843.  
  844.     else {                        /* set range of days */
  845.         strcpy(tmp, day);
  846.         if ((p = strchr(tmp, '-')) != NULL)
  847.             *p++ = '\0';
  848.  
  849.         /* parse single day or range of days - return if error */
  850.         if ((i = get_weekday(tmp, FALSE)) == NOT_WEEKDAY ||
  851.             (j = p ? get_weekday(p, FALSE) : i) == NOT_WEEKDAY)
  852.             return;
  853.  
  854.         /* set day colors, wrapping around end of week */
  855.         for (j = (i > j) ? j + 7 : j; i <= j; i++)
  856.             day_color[i % 7] = col;
  857.     }
  858.  
  859.     /* reset weekday_color to most prevalent color */
  860.  
  861.     for (i = 0; i < NUM_COLORS; i++)        /* clear counts */
  862.         count[i] = 0;
  863.  
  864.     for (i = SUN; i <= SAT; i++)            /* count colors */
  865.         count[day_color[i]]++;
  866.  
  867.     for (i = max = 0; i < NUM_COLORS; i++)        /* get most prevalent */
  868.         if (count[i] > max)
  869.             max = count[weekday_color = i];
  870.  
  871. }
  872.  
  873.  
  874. /*
  875.  * change_color - reset existing non-black colors to selected color
  876.  * (for backward-compatibility with pre-v4.4 -G and -O flags)
  877.  */
  878. void
  879. #ifdef PROTOS
  880. change_color(int col)
  881. #else
  882. change_color(col)
  883.     int  col;
  884. #endif
  885. {
  886.     int i;
  887.  
  888.     for (i = SUN; i <= SAT; i++)
  889.         if (day_color[i] != BLACK)
  890.             day_color[i] = col;
  891.  
  892.     if (holiday_color != BLACK && holiday_color != HOLIDAY_DEFAULT)
  893.         holiday_color = col;
  894.  
  895. }
  896.  
  897.  
  898. /*
  899.  * get_flag() - look up flag in flag_tbl; return pointer to its entry
  900.  * (NULL if not found)
  901.  */
  902. FLAG_USAGE *
  903. #ifdef PROTOS
  904. get_flag(char flag)
  905. #else
  906. get_flag(flag)
  907.     char flag;
  908. #endif
  909. {
  910.     FLAG_USAGE *pflag;
  911.  
  912.     for (pflag = flag_tbl; pflag->flag; pflag++)
  913.         if (flag == pflag->flag)
  914.             return pflag;
  915.  
  916.     return flag ? NULL : pflag;        /* '\0' is a valid flag */
  917. }
  918.  
  919.  
  920. /*
  921.  * set_debug_flag - look up "flag" in debug flag table and set selected
  922.  * bits in debug flag word (clear word if "flag" is NULL)
  923.  */
  924. void
  925. #ifdef PROTOS
  926. set_debug_flag(char *flag)
  927. #else
  928. set_debug_flag(flag)
  929.     char *flag;
  930. #endif
  931. {
  932.     char f;
  933.     DEBUG_INFO *pd;
  934.  
  935.     if (!flag) {            /* clear all if NULL */
  936.         debug_flags = 0;
  937.         return;
  938.     }
  939.  
  940.     /* loop through all characters in "flag", setting corresponding
  941.      * bits in debug_flags
  942.      */
  943.     while (f = *flag++)
  944.         for (pd = debug_info; pd->flag; pd++)
  945.             if (pd->flag == f)
  946.                 debug_flags |= pd->value;
  947.  
  948. }
  949.  
  950. /*
  951.  * get_args - walk the argument list, parsing all arguments but processing only
  952.  * those specified (in flag_tbl[]) to be processed this pass; return TRUE if
  953.  * OK, FALSE if invalid flag found.
  954.  */
  955. int
  956. #ifdef PROTOS
  957. get_args(char **argv,        /* argument list */
  958.      int  curr_pass,    /* current pass (P_xxx) */
  959.      char *where,        /* for error messages */
  960.      int  get_numargs)    /* parse and save numeric arguments? */
  961. #else
  962. get_args(argv, curr_pass, where, get_numargs)
  963.     char **argv;        /* argument list */
  964.     int  curr_pass;        /* current pass (P_xxx) */
  965.     char *where;        /* for error messages */
  966.     int  get_numargs;    /* parse and save numeric arguments? */
  967. #endif
  968. {
  969.     char *parg, *opt, *p, *pass, tmpbuf[STRSIZ];
  970.     FLAG_USAGE *pflag, *pf;
  971.     int i, flag, sv_debug;
  972.     int flags_ok = TRUE;        /* return value */
  973.     FILE *fp = stdout;        /* for piping "help" message */
  974.  
  975. /*
  976.  * If argument follows flag (immediately or as next parameter), return
  977.  * pointer to it (and bump argv if necessary); else return NULL
  978.  */
  979. #define GETARG() (*(*argv + 2) ? *argv + 2 : \
  980.           (*(argv+1) && **(argv+1) != '-' ? *++argv : NULL))
  981.  
  982.     /* set up pass name to print in debug messages */
  983.  
  984.     pass = where ? where :
  985.            curr_pass == P_CMD0 ? "CMD0" :
  986.            curr_pass == P_ENV  ? "ENV"  :
  987.            curr_pass == P_CMD1 ? "CMD1" :
  988.            curr_pass == P_CMD2 ? "CMD2" :
  989.                          "OPT"  ;
  990.  
  991.     /* Walk argument list, ignoring first element (program name) */
  992.  
  993.      while (opt = *++argv) {
  994.  
  995.         /* Assume that any non-flag argument is a numeric argument */
  996.         if (*opt != '-') {
  997.                 if (get_numargs && nargs < MAXARGS) {
  998.                 if (! IS_NUMERIC(opt))
  999.                     goto bad_par;
  1000.                 numargs[nargs++] = atoi(opt);
  1001.                 if (DEBUG(DEBUG_OPTS))
  1002.                     FPR(stderr, "%s: %s\n", pass, opt);
  1003.             }
  1004.             continue;
  1005.         }
  1006.  
  1007.         /* Check that flag is a) legal, and b) to be processed this pass */
  1008.  
  1009.         if (! (pflag = get_flag(flag = opt[1])) )
  1010.             goto bad_par;
  1011.  
  1012.         /* get optional argument even if flag not processed this pass */
  1013.  
  1014.         parg = pflag->has_arg ? GETARG() : NULL;
  1015.  
  1016.         if (! (pflag->passes & curr_pass)) {    /* skip flag this pass? */
  1017.             if (curr_pass == P_OPT)
  1018.                 FPR(stderr, E_FLAG_IGNORED, progname, flag,
  1019.                     DATE_FILE, where);
  1020.             continue;
  1021.         }
  1022.  
  1023.         /* echo pass name and flag if debugging on */
  1024.         if (DEBUG(DEBUG_OPTS))
  1025.             FPR(stderr, "%s: -%c%s%s\n", pass, flag,
  1026.                 parg ? " " : "", parg ? parg : "");
  1027.  
  1028.         /* convert escape sequences in command-line parameters */
  1029.         if (parg && (curr_pass == P_CMD1 || curr_pass == P_CMD2)) {
  1030.             cvt_escape(tmpbuf, parg);
  1031.             parg = tmpbuf;
  1032.         }
  1033.  
  1034.         switch (flag) {
  1035.  
  1036.         case F_INITIALIZE:    /* reset all flags to defaults */
  1037.  
  1038.             /* set up a command line to reset all of the
  1039.              * flags; call get_args() recursively to parse it
  1040.              * (note that some of the flags must be reset
  1041.              * explicitly, as no command-line flags exist to
  1042.              * reset them)
  1043.              */
  1044.  
  1045.             /* reset flags described above */
  1046.             julian_dates  = JULIAN_DATES;
  1047.             draw_moons    = DRAW_MOONS;
  1048.             do_whole_year = DO_WHOLE_YEAR;
  1049.             blank_boxes   = BLANK_BOXES;
  1050.             calendar_out  = CALENDAR_OUT;
  1051.             small_cal_pos = SMALL_CAL_POS;
  1052.  
  1053.             /* select program default for landscape/portrait
  1054.              * mode (must be done first because -[xXyY] depend
  1055.              * on it) and US/European date styles
  1056.              */
  1057.             sprintf(lbuf, "pcal -%c -%c",
  1058. #if (ROTATE == LANDSCAPE)
  1059.                 F_LANDSCAPE,
  1060. #else
  1061.                 F_PORTRAIT,
  1062. #endif
  1063. #if (DATE_STYLE == USA_DATES)
  1064.                 F_USA_DATES);
  1065. #else
  1066.                 F_EUR_DATES);
  1067. #endif
  1068.             p = lbuf + strlen(lbuf);
  1069.  
  1070.             /* all other flags take arguments and are reset
  1071.              * by specifying the flag without an argument
  1072.              */
  1073.             for (pf = flag_tbl; pf->flag; pf++)
  1074.                 if ((pf->passes & curr_pass) && pf->has_arg) {
  1075.                     sprintf(p, " -%c", pf->flag);
  1076.                     p += strlen(p);
  1077.                 }
  1078.  
  1079.             /* split new command line into words; parse it */
  1080.             (void) loadwords(words, lbuf);
  1081.             (void) get_args(words, curr_pass, NULL, FALSE);
  1082.  
  1083.             /* -G and -O without arguments change some day
  1084.              * colors (for backward-compatibility with
  1085.              * pre-v4.4 versions), so initialize the day 
  1086.              * colors explicitly
  1087.              */
  1088.             INIT_COLORS;
  1089.  
  1090.             /* also disable the time zone flag explicitly */
  1091.             tz_flag = FALSE;
  1092.  
  1093.             break;
  1094.  
  1095.         case F_BLACK_DAY:    /* print day in black or gray */
  1096.         case F_GRAY_DAY:
  1097.             if (parg)
  1098.                 set_color(parg, flag == F_BLACK_DAY ?
  1099.                           BLACK : GRAY);
  1100.             else
  1101.                 INIT_COLORS;    /* reset to defaults */
  1102.             break;
  1103.  
  1104.         case F_OUTLINE:        /* print day in outline or outline-gray */
  1105.         case F_OUTLINE_GRAY:
  1106.             /* ignore "-Gall" etc. on second command-line pass;
  1107.              * this is to avoid changing the definition of
  1108.              * "weekday", et. al. after the date file has already
  1109.              * been read
  1110.              */
  1111.             if (parg) {
  1112.                 if (curr_pass != P_CMD2)
  1113.                     set_color(parg, flag == F_OUTLINE ?
  1114.                             OUTLINE :
  1115.                             OUTLINE_GRAY);
  1116.             }
  1117.             else
  1118.                 /* special hack for backward compatibility
  1119.                  * with v4.3 and earlier: -G or -O alone
  1120.                  * change all non-black days to the selected
  1121.                  * color
  1122.                  */
  1123.                 change_color(flag == F_OUTLINE ?
  1124.                         OUTLINE : OUTLINE_GRAY);
  1125.  
  1126.             break;
  1127.  
  1128.          case F_DAY_FONT:    /* specify alternate day font */
  1129.             define_font(datefont, parg, DATEFONT);
  1130.              break;
  1131.  
  1132.         case F_NOTES_FONT:    /* specify alternate notes font */
  1133.             define_font(notesfont, parg, NOTESFONT);
  1134.             break;
  1135.  
  1136.          case F_TITLE_FONT:    /* specify alternate title font */
  1137.             define_font(titlefont, parg, TITLEFONT);
  1138.              break;
  1139.  
  1140.         case F_REMAP_FONT:    /* specify 8-bit font mapping */
  1141.             if (parg) {
  1142.                 if (ci_strncmp(parg, MAPPING_R, 1) == 0)
  1143.                     mapfonts = ROMAN8;
  1144.                 else if (ci_strncmp(parg, MAPPING_L, 1) == 0)
  1145.                     mapfonts = LATIN1;
  1146.                 else
  1147.                     mapfonts = NOMAP;
  1148.             } else
  1149.                 mapfonts = MAPFONTS;
  1150.             break;
  1151.  
  1152.         case F_EMPTY_CAL:    /* generate empty calendar */
  1153.             datefile_type = NO_DATEFILE;
  1154.             strcpy(datefile, "");
  1155.             break;
  1156.  
  1157.         case F_DATE_FILE:    /* specify alternate date file */
  1158.             datefile_type = parg ? USER_DATEFILE : SYS_DATEFILE;
  1159.             strcpy(datefile, parg ? parg : "");
  1160.             break;
  1161.  
  1162.         case F_OUT_FILE:    /* specify alternate output file */
  1163.             strcpy(outfile, parg ? parg : OUTFILE);
  1164.             break;
  1165.  
  1166.         case F_LANDSCAPE:    /* generate landscape calendar */
  1167.              rotate = LANDSCAPE;
  1168.             strcpy(xsval, XSVAL_L);
  1169.             strcpy(ysval, YSVAL_L);
  1170.             strcpy(xtval, XTVAL_L);
  1171.             strcpy(ytval, YTVAL_L);
  1172.              break;
  1173.  
  1174.         case F_PORTRAIT:    /* generate portrait calendar */
  1175.              rotate = PORTRAIT;
  1176.             strcpy(xsval, XSVAL_P);
  1177.             strcpy(ysval, YSVAL_P);
  1178.             strcpy(xtval, XTVAL_P);
  1179.             strcpy(ytval, YTVAL_P);
  1180.              break;
  1181.  
  1182.         case F_HELP:        /* request version info and/or help */
  1183.         case F_USAGE:
  1184.         case F_VERSION:
  1185.             /* PAGER_ENV (cf. pcaldefs.h) defines the name of an
  1186.              * environment variable which, if set, points to the
  1187.              * appropriate pager (e.g., "more", "less", "pg")
  1188.              * for piping the "help" message (Un*x systems only)
  1189.              */
  1190. #ifdef PAGER_ENV
  1191.             if (flag == F_HELP) {
  1192.                 FILE *pfp;
  1193.                 char *pager, *p;
  1194.  
  1195.                 pager = (p = getenv(PAGER_ENV)) ? p :
  1196.                     PAGER_DEFAULT;
  1197.                 /* null pointer or string: no paging */
  1198.                 if (pager && *pager &&
  1199.                     (pfp = popen(pager, "w")) != (FILE *)NULL)
  1200.                     fp = pfp;
  1201.             }
  1202. #endif
  1203.             FPR(fp, "%s\n", VERSION_STRING + 4);
  1204.             if (flag != F_VERSION)
  1205.                 usage(fp, flag == F_HELP);
  1206.             fflush(fp);
  1207. #ifdef PAGER_ENV
  1208.             if (fp != stdout)
  1209.                 pclose(fp);
  1210. #endif
  1211.             exit(EXIT_SUCCESS);
  1212.             break;
  1213.  
  1214.         case F_MOON_4:        /* draw four moons */
  1215.         case F_MOON_ALL:    /* draw a moon for each day */
  1216.             draw_moons = flag == F_MOON_ALL ? ALL_MOONS : SOME_MOONS;
  1217.             break;
  1218.  
  1219.         case F_DEFINE:        /* define preprocessor symbol */
  1220.             (void) do_define(parg);
  1221.             break;
  1222.  
  1223.         case F_UNDEF:        /* undef preprocessor symbol */
  1224.             (void) do_undef(parg);
  1225.             break;
  1226.  
  1227.         case F_L_FOOT:        /* specify alternate left foot */
  1228.             strcpy(lfoot, parg ? parg : LFOOT);
  1229.             break;
  1230.  
  1231.         case F_C_FOOT:        /* specify alternate center foot */
  1232.             strcpy(cfoot, parg ? parg : CFOOT);
  1233.             break;
  1234.  
  1235.          case F_R_FOOT:        /* specify alternate right foot */
  1236.             strcpy(rfoot, parg ? parg : RFOOT);
  1237.             break;
  1238.  
  1239.          case F_NOTES_HDR:    /* specify alternate notes header */
  1240.             strcpy(notes_hdr, parg ? parg : default_notes_hdr);
  1241.             break;
  1242.  
  1243.         case F_FIRST_DAY:    /* select starting day of week */
  1244.             if (parg) {
  1245.                 if ((i = get_weekday(parg, FALSE)) != NOT_WEEKDAY)
  1246.                     first_day_of_week = i;
  1247.             }
  1248.             else
  1249.                 first_day_of_week = FIRST_DAY;
  1250.             break;
  1251.  
  1252.         case F_USA_DATES:    /* select American date style */
  1253.         case F_EUR_DATES:    /* select European date style */
  1254.             date_style = flag == F_USA_DATES ? USA_DATES : EUR_DATES;
  1255.             break;
  1256.  
  1257.         case F_X_TRANS:        /* set x-axis translation factor */
  1258.             strcpy(xtval, parg ? parg :
  1259.                 (rotate == LANDSCAPE ? XTVAL_L : XTVAL_P));
  1260.             break;
  1261.  
  1262.         case F_Y_TRANS:        /* set y-axis translation factor */
  1263.             strcpy(ytval, parg ? parg :
  1264.                 (rotate == LANDSCAPE ? YTVAL_L : YTVAL_P));
  1265.             break;
  1266.  
  1267.         case F_X_SCALE:        /* set x-axis scaling factor */
  1268.             strcpy(xsval, parg ? parg :
  1269.                 (rotate == LANDSCAPE ? XSVAL_L : XSVAL_P));
  1270.             break;
  1271.  
  1272.         case F_Y_SCALE:        /* set y-axis scaling factor */
  1273.             strcpy(ysval, parg ? parg :
  1274.                 (rotate == LANDSCAPE ? YSVAL_L : YSVAL_P));
  1275.             break;
  1276.  
  1277.         case F_JULIAN:        /* display Julian dates */
  1278.         case F_JULIAN_ALL:    /* Julian date/days remaining */
  1279.             julian_dates = flag == F_JULIAN_ALL ? ALL_JULIANS :
  1280.                                   SOME_JULIANS;
  1281.             break;
  1282.  
  1283.         case F_WHOLE_YEAR:    /* print whole year at once */
  1284.             do_whole_year = !(DO_WHOLE_YEAR);
  1285.             (void) do_define(DEF_WHOLE_YEAR);
  1286.             break;
  1287.  
  1288.         case F_CALENDAR:    /* generate "calendar(1)" input */
  1289.             calendar_out = !(CALENDAR_OUT);
  1290.             break;
  1291.  
  1292.         case F_BLANK_BOXES:    /* suppress shading unused boxes */
  1293.             blank_boxes = !(BLANK_BOXES);
  1294.             break;
  1295.  
  1296.         case F_NUM_PAGES:    /* print multiple copies of each page */
  1297.             strcpy(ncopy, parg ? parg : NCOPY);
  1298.             break;
  1299.  
  1300.         case F_SC_NONE:        /* suppress small calendars */
  1301.             small_cal_pos = SC_NONE;
  1302.             break;
  1303.  
  1304.         case F_SC_FIRST:    /* reposition of small calendars */
  1305.         case F_SC_SPLIT:
  1306.             small_cal_pos = flag == F_SC_FIRST ? SC_FIRST :
  1307.                                  SC_SPLIT;
  1308.             break;
  1309.  
  1310.         case F_SHADING:        /* set date/fill shading levels */
  1311.             define_shading(shading, parg, SHADING);
  1312.             break;
  1313.  
  1314.         case F_TIMEZONE:    /* select alternate time zone */
  1315.             strcpy(time_zone, parg ? parg : TIMEZONE);
  1316.             tz_flag = TRUE;
  1317.             break;
  1318.  
  1319.         case F_DEBUG:        /* turn on debugging (undocumented) */
  1320.             sv_debug = DEBUG(DEBUG_OPTS);
  1321.             set_debug_flag(parg);
  1322.  
  1323.             /* print -ZO flag if first time set */
  1324.             if (!sv_debug && DEBUG(DEBUG_OPTS))
  1325.                 FPR(stderr, "%s: -%c%s\n", pass, flag,
  1326.                     parg ? parg : "");
  1327.             break;
  1328.  
  1329.         case '-' :        /* accept - and -- as dummy flags */
  1330.         case '\0':
  1331.             break;
  1332.  
  1333.         default:        /* missing case label if reached!!! */
  1334.  
  1335. bad_par:                /* unrecognized parameter */
  1336.  
  1337.             FPR(stderr, E_ILL_OPT, progname, opt);
  1338.             if (where)
  1339.                 FPR(stderr, E_ILL_OPT2,
  1340.                     curr_pass == P_ENV ? ENV_VAR :
  1341.                     curr_pass == P_OPT ? DATE_FILE : "",
  1342.                     where);
  1343.             FPR(stderr, "\n");
  1344.             flags_ok = FALSE;
  1345.             break;
  1346.         }
  1347.         }
  1348.  
  1349.     /* if we read the numeric arguments, validate and interpret them */
  1350.     if (get_numargs)
  1351.         flags_ok &= check_numargs();
  1352.  
  1353.     return flags_ok;
  1354. }
  1355.  
  1356.  
  1357. /*
  1358.  * check_numargs - validate and interpret numeric command-line parameters;
  1359.  * return TRUE if all OK, print error message and return FALSE if not
  1360.  */
  1361. int
  1362. #ifdef PROTOS
  1363. check_numargs(void)
  1364. #else
  1365. check_numargs()
  1366. #endif
  1367. {
  1368.     /* Validate non-flag (numeric) parameters */
  1369.  
  1370.     struct tm *p_tm;    /* for getting current month/year */
  1371.     time_t tmp;
  1372.     int params_ok = TRUE;    /* return value */
  1373.  
  1374.     switch (nargs) {
  1375.     case 0:        /* no arguments - print current month and/or year */
  1376.         time(&tmp);
  1377.         p_tm = localtime(&tmp);
  1378.         init_month = p_tm->tm_mon + 1;
  1379.         init_year = p_tm->tm_year;
  1380.         nmonths = 1;
  1381.         break;
  1382.  
  1383.     case 1:        /* one argument - print entire year */
  1384.         init_month = JAN;
  1385.         init_year = numargs[0];
  1386.         nmonths = 12;
  1387.         break;
  1388.  
  1389.     default:    /* two or three arguments - print one or more months */
  1390.         init_month = numargs[0];
  1391.         init_year = numargs[1];
  1392.         nmonths = nargs > 2 ? numargs[2] : 1;
  1393.         break;
  1394.     }
  1395.  
  1396.     if (nmonths < 1)        /* ensure at least one month */
  1397.         nmonths = 1;
  1398.  
  1399.     /* check range of month and year */
  1400.  
  1401.     if (init_month < JAN || init_month > DEC) {
  1402.         FPR(stderr, E_ILL_MONTH, progname, init_month, JAN, DEC);
  1403.         params_ok = FALSE;
  1404.     }
  1405.     
  1406.     if (init_year > 0 && init_year < 100)    /* treat nn as 19nn */
  1407.         init_year += CENTURY;
  1408.     
  1409.     if (init_year < MIN_YR || init_year > MAX_YR) {
  1410.         FPR(stderr, E_ILL_YEAR, progname, init_year, MIN_YR, MAX_YR);
  1411.         params_ok = FALSE;
  1412.     }
  1413.  
  1414.     return params_ok;
  1415. }
  1416.  
  1417.  
  1418.  
  1419. /*
  1420.  * color_msg - return character string explaining default day colors;
  1421.  * assumes that defaults consist of black and at most one other color
  1422.  */
  1423. char *
  1424. #ifdef PROTOS
  1425. color_msg(void)
  1426. #else
  1427. color_msg()
  1428. #endif
  1429. {
  1430.     int i, ngray = 0, others, gray;
  1431.     static char msg[80], *alt_color;
  1432.  
  1433.     for (i = SUN; i <= SAT; i++)    /* count "logical gray" weekdays */
  1434.         if (default_color[i] != BLACK) {
  1435.             gray = default_color[i];
  1436.             ngray++;
  1437.             }
  1438.  
  1439.     alt_color = color_names[gray];    /* map "logical gray" to its name */
  1440.  
  1441.     if (ngray == 0 || ngray == 7) {        /* all same color? */
  1442.         sprintf(msg, COLOR_MSG_1, ngray ? alt_color : W_BLACK);
  1443.         return msg;
  1444.     }
  1445.  
  1446.     others = ngray <= 3 ? BLACK : gray;    /* no - get predominant color */
  1447.     msg[0] = '\0';
  1448.     for (i = SUN; i <= SAT; i++)
  1449.         if (default_color[i] != others) {
  1450.             strncat(msg, days[i], MIN_DAY_LEN);
  1451.             strcat(msg, "/");
  1452.         }
  1453.     LASTCHAR(msg) = ' ';
  1454.  
  1455.     sprintf(msg + strlen(msg), COLOR_MSG_2,
  1456.         others == BLACK ? alt_color : W_BLACK,
  1457.                 others == BLACK ? W_BLACK : alt_color);
  1458.     return msg;
  1459. }
  1460.  
  1461.  
  1462. /*
  1463.  * usage - print message explaining correct usage of the command-line
  1464.  * arguments and flags.  If "fullmsg" is true, print associated text
  1465.  */
  1466. void
  1467. #ifdef PROTOS
  1468. usage(FILE *fp,        /* destination of usage message */
  1469.       int fullmsg)    /* print complete message? */
  1470. #else
  1471. usage(fp, fullmsg)
  1472.     FILE *fp;    /* destination of usage message */
  1473.     int fullmsg;    /* print complete message? */
  1474. #endif
  1475. {
  1476.     FLAG_MSG *pflag;
  1477.     PARAM_MSG *ppar;
  1478.     DATE_MSG *pdate;
  1479.     KWD_H *phol;
  1480.     char buf[30], *p, flag, *meta;
  1481.     int nchars, first, i, indent, n;
  1482.  
  1483.     sprintf(buf, "%s: %s", W_USAGE, progname);
  1484.     nchars = indent = strlen(buf);
  1485.     first = TRUE;
  1486.     meta = p = NULL;
  1487.     FPR(fp, "\n%s", buf);
  1488.  
  1489.     /* loop to print command-line syntax message (by group of flags) */
  1490.  
  1491.     for (pflag = flag_msg; (flag = pflag->flag) != '\0'; pflag++) {
  1492.         if (flag == '\n') {        /* end of group? */
  1493.             if (p)
  1494.                 *p = '\0';
  1495.             if (meta) {        /* append metavariable name */
  1496.                 strcat(buf, " ");
  1497.                 strcat(buf, meta);
  1498.             }
  1499.             strcat(buf, "]");
  1500.             n = strlen(buf);
  1501.             if (nchars + n > SCREENWIDTH) {    /* does it fit on line? */
  1502.                 FPR(fp, "\n");        /* no - start new one */
  1503.                 for (nchars = 0; nchars < indent; nchars++)
  1504.                     FPR(fp, " ");
  1505.             }
  1506.             FPR(fp, "%s", buf);
  1507.             nchars += n;
  1508.             first = TRUE;
  1509.         }
  1510.         else if (flag != ' ') {        /* accumulate flags for group */
  1511.             if (first) {
  1512.                 sprintf(buf, " [");
  1513.                 p = buf + strlen(buf);
  1514.             }
  1515.             else
  1516.                 *p++ = '|';
  1517.             *p++ = '-';
  1518.             *p++ = flag;
  1519.             meta = pflag->meta;    /* save metavariable name */
  1520.             first = FALSE;
  1521.         }
  1522.     }
  1523.  
  1524.     /* loop to print selected numeric parameter descriptions */
  1525.  
  1526.     for (i = 0; i < PARAM_MSGS; i++) {
  1527.         sprintf(buf, " [%s]%s", param_msg[i].desc,
  1528.             i < PARAM_MSGS - 1 ? " |" : "");
  1529.         n = strlen(buf);
  1530.         if (nchars + n > SCREENWIDTH) {    /* does it fit on line? */
  1531.             FPR(fp, "\n");        /* no - start new one */
  1532.             for (nchars = 0; nchars < indent; nchars++)
  1533.                 FPR(fp, " ");
  1534.         }
  1535.         FPR(fp, "%s", buf);
  1536.         nchars += n;
  1537.     }
  1538.  
  1539.     FPR(fp, "\n\n");
  1540.     if (! fullmsg) {
  1541.         FPR(fp, USAGE_MSG, progname, F_HELP);
  1542.         return;
  1543.     }
  1544.     
  1545.     /* loop to print the full flag descriptions */
  1546.  
  1547.     for (pflag = flag_msg; (flag = pflag->flag) != '\0'; pflag++) {
  1548.         if (flag == '\n') {    /* newline?  print and quit */
  1549.             FPR(fp, "\n");
  1550.             continue;
  1551.         }
  1552.         p = buf;        /* copy flag and metavariable to buffer */
  1553.         if (flag != ' ')
  1554.             *p++ = '-';
  1555.     /* special hack for VMS - surround upper-case flags in quotes */
  1556. #ifdef VMS
  1557.         if (isupper(flag)) {
  1558.             *p++ = '"';
  1559.             *p++ = flag;
  1560.             *p++ = '"';
  1561.         }
  1562.         else
  1563.             *p++ = flag;
  1564. #else
  1565.         *p++ = flag;
  1566. #endif
  1567.         *p = '\0';
  1568.         if (pflag->meta)
  1569.             sprintf(p, " %s", pflag->meta);
  1570.         FPR(fp, "\t%-20.20s", buf);
  1571.         if (pflag->text)
  1572.             FPR(fp, "%s", pflag->text);
  1573.  
  1574.         /* print default value if specified */
  1575.         if (pflag->def)
  1576.             FPR(fp, " (%s: %s)", W_DEFAULT, pflag->def[0] ? pflag->def : "\"\"" );
  1577.         FPR(fp, "\n");
  1578.  
  1579.         /* special case - print color messages after F_OUTLINE_GRAY */
  1580.         if (flag == F_OUTLINE_GRAY)
  1581.             FPR(fp, "\t%20s  (%s: %s)\n", "", W_DEFAULT, color_msg());
  1582.  
  1583.     }
  1584.     
  1585.     /* now print the information about the numeric parameters */
  1586.  
  1587.     for (ppar = param_msg; ppar->desc; ppar++)
  1588.         FPR(fp, "\t%-16.16s%s\n", ppar->desc, ppar->text);
  1589.     
  1590.     /* print the date file syntax message */
  1591.  
  1592.     FPR(fp, "\n");
  1593.     for (pdate = date_msg; *pdate; pdate++)
  1594.         FPR(fp, "\t%s\n", *pdate);
  1595.  
  1596.     /* print list of predefined holidays */
  1597.     FPR(fp, "\n\t%s\n\n", PREDEF_MSG_1);
  1598. #define N 4
  1599.     for (phol = holidays, n = 0; phol->name; phol++, n = (n + 1) % N)
  1600.         FPR(fp, "%s%-15.15s %s", n == 0 ? "\t    " : "", phol->name,
  1601.             (n == N - 1) || !(phol[1].name) ? "\n" : "");
  1602.     FPR(fp, "\n\t%s\n", PREDEF_MSG_2);
  1603.     FPR(fp, "\n");
  1604.  
  1605. }
  1606.  
  1607.  
  1608. /*
  1609.  * alt_fopen - attempt to open a file in one of several paths in a
  1610.  * NULL-terminated path list.  If successful, return (opened) file pointer
  1611.  * and fill in full path name; if not, return NULL
  1612.  */
  1613. FILE *
  1614. #ifdef PROTOS
  1615. alt_fopen(char *fullpath,    /* full path name (output) */
  1616.       char *name,        /* base name (or full path spec) */
  1617.       char *pathlist[],    /* NULL-terminated path list */
  1618.       char *access)        /* permission requested */
  1619. #else
  1620. alt_fopen(fullpath, name, pathlist, access)
  1621.     char *fullpath;        /* full path name (output) */
  1622.     char *name;        /* base name (or full path spec) */
  1623.     char *pathlist[];    /* NULL-terminated path list */
  1624.     char *access;        /* permission requested */
  1625. #endif
  1626. {
  1627.     char **path;
  1628.     FILE *fp;
  1629.  
  1630.     if (DEBUG(DEBUG_PATHS)) {
  1631.         FPR(stderr, "Searching for %s in the following paths:\n",
  1632.             name);
  1633.         for (path = pathlist; *path; path++)
  1634.             FPR(stderr, "  %s\n", **path ? *path : ".");
  1635.     }
  1636.  
  1637.     for (path = pathlist; *path; path++) {
  1638.         mk_filespec(fullpath, *path, name);
  1639.         if ((fp = fopen(fullpath, access)) != NULL) {
  1640.             if (DEBUG(DEBUG_PATHS))
  1641.                 FPR(stderr, "found %s\n", fullpath);
  1642.             return fp;
  1643.         }
  1644.     }
  1645.  
  1646.     fullpath[0] = '\0';        /* file not found */
  1647.     return NULL;
  1648. }
  1649.